/*
 * FreeRTOS V202212.00
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

/*
 * This demo application file demonstrates the use of queues to pass data
 * between co-routines.
 *
 * N represents the number of 'fixed delay' co-routines that are created and
 * is set during initialisation.
 *
 * N 'fixed delay' co-routines are created that just block for a fixed
 * period then post the number of an LED onto a queue.  Each such co-routine
 * uses a different block period.  A single 'flash' co-routine is also created
 * that blocks on the same queue, waiting for the number of the next LED it
 * should flash.  Upon receiving a number it simply toggle the instructed LED
 * then blocks on the queue once more.  In this manner each LED from LED 0 to
 * LED N-1 is caused to flash at a different rate.
 *
 * The 'fixed delay' co-routines are created with co-routine priority 0.  The
 * flash co-routine is created with co-routine priority 1.  This means that
 * the queue should never contain more than a single item.  This is because
 * posting to the queue will unblock the 'flash' co-routine, and as this has
 * a priority greater than the tasks posting to the queue it is guaranteed to
 * have emptied the queue and blocked once again before the queue can contain
 * any more date.  An error is indicated if an attempt to post data to the
 * queue fails - indicating that the queue is already full.
 *
 */

/* Scheduler includes. */
#include "FreeRTOS.h"
#include "croutine.h"
#include "queue.h"

/* Demo application includes. */
#include "partest.h"
#include "crflash.h"

/* The queue should only need to be of length 1.  See the description at the
 * top of the file. */
#define crfQUEUE_LENGTH            1

#define crfFIXED_DELAY_PRIORITY    0
#define crfFLASH_PRIORITY          1

/* Only one flash co-routine is created so the index is not significant. */
#define crfFLASH_INDEX             0

/* Don't allow more than crfMAX_FLASH_TASKS 'fixed delay' co-routines to be
 * created. */
#define crfMAX_FLASH_TASKS         8

/* We don't want to block when posting to the queue. */
#define crfPOSTING_BLOCK_TIME      0

#if ( configUSE_CO_ROUTINES == 1 )

/*
 * The 'fixed delay' co-routine as described at the top of the file.
 */
    static void prvFixedDelayCoRoutine( CoRoutineHandle_t xHandle,
                                        UBaseType_t uxIndex );

/*
 * The 'flash' co-routine as described at the top of the file.
 */
    static void prvFlashCoRoutine( CoRoutineHandle_t xHandle,
                                   UBaseType_t uxIndex );

/* The queue used to pass data between the 'fixed delay' co-routines and the
 * 'flash' co-routine. */
    static QueueHandle_t xFlashQueue;

/* This will be set to pdFALSE if we detect an error. */
    static BaseType_t xCoRoutineFlashStatus = pdPASS;

/*-----------------------------------------------------------*/

/*
 * See the header file for details.
 */
    void vStartFlashCoRoutines( UBaseType_t uxNumberToCreate )
    {
        UBaseType_t uxIndex;

        if( uxNumberToCreate > crfMAX_FLASH_TASKS )
        {
            uxNumberToCreate = crfMAX_FLASH_TASKS;
        }

        /* Create the queue used to pass data between the co-routines. */
        xFlashQueue = xQueueCreate( crfQUEUE_LENGTH, sizeof( UBaseType_t ) );

        if( xFlashQueue )
        {
            /* Create uxNumberToCreate 'fixed delay' co-routines. */
            for( uxIndex = 0; uxIndex < uxNumberToCreate; uxIndex++ )
            {
                xCoRoutineCreate( prvFixedDelayCoRoutine, crfFIXED_DELAY_PRIORITY, uxIndex );
            }

            /* Create the 'flash' co-routine. */
            xCoRoutineCreate( prvFlashCoRoutine, crfFLASH_PRIORITY, crfFLASH_INDEX );
        }
    }
/*-----------------------------------------------------------*/

    static void prvFixedDelayCoRoutine( CoRoutineHandle_t xHandle,
                                        UBaseType_t uxIndex )
    {
/* Even though this is a co-routine the xResult variable does not need to be
 * static as we do not need it to maintain its state between blocks. */
        BaseType_t xResult;

/* The uxIndex parameter of the co-routine function is used as an index into
 * the xFlashRates array to obtain the delay period to use. */
        static const TickType_t xFlashRates[ crfMAX_FLASH_TASKS ] =
        {
            150 / portTICK_PERIOD_MS,
            200 / portTICK_PERIOD_MS,
            250 / portTICK_PERIOD_MS,
            300 / portTICK_PERIOD_MS,
            350 / portTICK_PERIOD_MS,
            400 / portTICK_PERIOD_MS,
            450 / portTICK_PERIOD_MS,
            500 / portTICK_PERIOD_MS
        };

        /* Co-routines MUST start with a call to crSTART. */
        crSTART( xHandle );

        for( ; ; )
        {
            /* Post our uxIndex value onto the queue.  This is used as the LED to
             * flash. */
            crQUEUE_SEND( xHandle, xFlashQueue, ( void * ) &uxIndex, crfPOSTING_BLOCK_TIME, &xResult );

            if( xResult != pdPASS )
            {
                /* For the reasons stated at the top of the file we should always
                 * find that we can post to the queue.  If we could not then an error
                 * has occurred. */
                xCoRoutineFlashStatus = pdFAIL;
            }

            crDELAY( xHandle, xFlashRates[ uxIndex ] );
        }

        /* Co-routines MUST end with a call to crEND. */
        crEND();
    }
/*-----------------------------------------------------------*/

    static void prvFlashCoRoutine( CoRoutineHandle_t xHandle,
                                   UBaseType_t uxIndex )
    {
/* Even though this is a co-routine the variable do not need to be
 * static as we do not need it to maintain their state between blocks. */
        BaseType_t xResult;
        UBaseType_t uxLEDToFlash;

        /* Co-routines MUST start with a call to crSTART. */
        crSTART( xHandle );
        ( void ) uxIndex;

        for( ; ; )
        {
            /* Block to wait for the number of the LED to flash. */
            crQUEUE_RECEIVE( xHandle, xFlashQueue, &uxLEDToFlash, portMAX_DELAY, &xResult );

            if( xResult != pdPASS )
            {
                /* We would not expect to wake unless we received something. */
                xCoRoutineFlashStatus = pdFAIL;
            }
            else
            {
                /* We received the number of an LED to flash - flash it! */
                vParTestToggleLED( uxLEDToFlash );
            }
        }

        /* Co-routines MUST end with a call to crEND. */
        crEND();
    }
/*-----------------------------------------------------------*/

    BaseType_t xAreFlashCoRoutinesStillRunning( void )
    {
        /* Return pdPASS or pdFAIL depending on whether an error has been detected
         * or not. */
        return xCoRoutineFlashStatus;
    }

#endif /* if ( configUSE_CO_ROUTINES == 1 ) */
